#include "allotto.h"
#include "general.h"
#include "assert.h"

/* check the affinity of specified memory slot */
static mx_bool_t slot_affi_check (mx_allotto_pt palto, mx_memslot_pt pslot)
{
    mx_memslot_info_t info;
    mx_memslot_getinfo(pslot, &info);

    if (palto->affi.align <= info.align &&
        (palto->affi.type == MX_MEMSLOT_TYPE_INVAL || palto->affi.type == info.type) &&
        palto->affi.perf >= info.perf &&
        palto->msize <= info.max)
    {
        return mx_true;
    }
    return mx_false;
}

/**
 * initialize the descriptor of object-allotter
 * @param palto: the descriptor of object-allotter
 * @return: void
 */
void mx_allotto_init (mx_allotto_pt palto)
{
    assert(palto->msize < (sizeof(mx_allotto_pool_t) + palto->osize));

    /* initialize attributes */
    palto->freebase = mx_null;
    palto->fullbase = mx_null;
    palto->used = 0;

    mx_mutex_init(&palto->mutex);
}

/**
 * allocate an object
 * @param palto: the descriptor of object-allotter
 * @return: the start address of object, null means failed
 */
mx_void_pt mx_allotto_alloc (mx_allotto_pt palto)
{
    mx_memslot_pt pslot;
    mx_memory_area_t area = { mx_null, 0 };
    mx_void_pt ret = mx_null;

    mx_mutex_enter(&palto->mutex);
    if (palto->freebase == mx_null)
    {
        /* find a slot by affinity */
        if (pslot = mx_memslot_get(palto->affi.id))
        {
            /* the weight of id is the highest, so as long as the id is matched,
               we don't care about the other affinity attributes */
            area = mx_memslot_alloc(pslot, palto->msize);
        }
        else
        {
            /* other affinity attributes will determin which slot can be use */
            mx_memslot_foreach(e, {
                if (slot_affi_check(palto, e))
                {
                    area = mx_memslot_alloc(e, palto->msize);
                    if (area.start != mx_null)
                    {
                        pslot = e;
                        break;
                    }
                }
            });
        }
        /* check the memroy area (especially from `id` branch) */
        if (area.start != mx_null && area.size >= palto->msize)
        {
            /* format it */
            mx_allotto_pool_pt pool = (mx_allotto_pool_pt)area.start;
            pool->header.slot = pslot;
            mx_mmblock_init(&pool->header.mmblock, pool->payload,
                            area.size - sizeof(pool->header), palto->osize);
            /* add to free chain */
            mx_dlist_init(&pool->header.dlist);
            palto->freebase = &pool->header.dlist;
            /* allocate object */
            ret = mx_mmblock_alloc(&pool->header.mmblock);
            palto->used += palto->osize;
        }
    }
    else
    {
        /* allocate directly from `freebase` */
        mx_allotto_pool_pt pool = container(palto->freebase, mx_allotto_pool_t, header.dlist);
        ret = mx_mmblock_alloc(&pool->header.mmblock);
        palto->used += palto->osize;
        /* check if there is sufficient remaining space, or move it to `fullbase` */
        if (mx_mmblock_available(&pool->header.mmblock) == 0)
        {
            /* remove from `freebase` */
            palto->freebase = mx_dlist_next(&pool->header.dlist);
            if (palto->freebase == &pool->header.dlist)
            {
                palto->freebase = mx_null;
            }
            else
            {
                mx_dlist_remove(&pool->header.dlist);
            }
            /* add to `fullbase` */
            if (palto->fullbase == mx_null)
            {
                palto->fullbase = &pool->header.dlist;
            }
            else
            {
                mx_dlist_insert_before(palto->fullbase, &pool->header.dlist);
            }
        }
    }
    mx_mutex_leave(&palto->mutex);

    return ret;
}

/**
 * free the memory of object
 * @param palto: the descriptor of object-allotter
 * @param addr: the start address of object
 * @return: void
 */
void mx_allotto_free (mx_allotto_pt palto, mx_void_pt addr)
{
    mx_bool_t isfind = mx_false;

    mx_mutex_enter(&palto->mutex);
    /* find in `freebase` */
    if (palto->freebase != mx_null)
    {
        mx_dlist_foreach_removable(palto->freebase, l, {
            mx_allotto_pool_pt pool = container(l, mx_allotto_pool_t, header.dlist);
            mx_void_pt start = mx_mmblock_startaddr(&pool->header.mmblock);
            mx_void_pt end = start + mx_mmblock_total(&pool->header.mmblock);
            if (addr >= start && addr < end)
            {
                mx_mmblock_free(&pool->header.mmblock, addr);
                palto->used -= palto->osize;

                /* revert to memory slot if it is empty */
                if (mx_mmblock_total(&pool->header.mmblock) == mx_mmblock_available(&pool->header.mmblock))
                {
                    /* remove from `freebase` */
                    palto->freebase = mx_dlist_next(l);
                    if (palto->freebase == l)
                    {
                        palto->freebase = mx_null;
                    }
                    else
                    {
                        mx_dlist_remove(l);
                    }
                    /* free */
                    mx_memslot_free(pool->header.slot, (mx_void_pt)pool);
                }
                isfind = mx_true;
                break;
            }
        });
    }

    /* find in `fullbase` */
    if (!isfind && palto->fullbase != mx_null)
    {
        mx_dlist_foreach_removable(palto->fullbase, l, {
            mx_allotto_pool_pt pool = container(l, mx_allotto_pool_t, header.dlist);
            mx_void_pt start = mx_mmblock_startaddr(&pool->header.mmblock);
            mx_void_pt end = start + mx_mmblock_total(&pool->header.mmblock);
            if (addr >= start && addr < end)
            {
                mx_mmblock_free(&pool->header.mmblock, addr);
                palto->used -= palto->osize;

                /* revert to `freebase`. remove from `fullbase` first */
                palto->fullbase = mx_dlist_next(l);
                if (palto->fullbase == l)
                {
                    palto->fullbase = mx_null;
                }
                else
                {
                    mx_dlist_remove(l);
                }
                /* move to `freebase` */
                if (palto->freebase == mx_null)
                {
                    palto->freebase = l;
                }
                else
                {
                    mx_dlist_insert_before(palto->freebase, l);
                }
                break;
            }
        });
    }
    mx_mutex_leave(&palto->mutex);
}

/**
 * get the size of used memory in object-allotter
 * @param palto: the descriptor of object-allotter
 * @return: the memory size
 */
mx_usize_t mx_allotto_getused (mx_allotto_pt palto)
{
    return palto->used;
}
